/**
	リスト選択に関する注意事項
	1) トラックボールによるクリック
		ListViewのOnItemClickリスナでイベントを受け取ることが可能。
	2) タッチによるクリック
		ListViewのOnClickリスナでイベントを受け取ることが可能だが、
		これだと何番目のリストがクリックされたのか分かりにくいため、
		ListAdapter内で生成したViewのOnClickリスナでイベントを受け取る。
*/
package com.nakaikensuke.SNE;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.List;

import junit.framework.Assert;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.markupartist.android.widget.PullToRefreshListView;
import com.nakaikensuke.tools.ItemAdapter;
import com.nakaikensuke.tools.ItemComposer;
import com.nakaikensuke.tools.ListBuilder;
import com.nakaikensuke.tools.MimeUtilities;

public class ShortnameExplorer extends Activity {
	///	List objects
	private List<DirEntry> _list = ListBuilder.createList();
	private ItemAdapter<DirEntry> _adapter = null;
	private DirCacheManager _cacheManager = new DirCacheManager();

	///	Some views
	private ProgressDialog _progress = null;
	private PullToRefreshListView _listView = null;
	private TextView _currentPathView = null;

	///	Status object.
	private boolean _isShort = false;
	private String _encodingMode = "";
	private String _currentPath = Environment.getExternalStorageDirectory().getPath();
	
	///	Thread objects for getting directory entries.
	private Thread _thread = null;
	private Handler _handler = new Handler();
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		_currentPathView = (TextView)findViewById(R.id.dir_pos_text);

		/// Custom list adapter generated.
		/// According to entry name mode, decide to display short or long names.
		_adapter = new ItemAdapter<DirEntry>(
				getApplicationContext(),
				R.layout.item,
				_list,
				new ItemComposer<DirEntry>() {
					@Override
					public void compose(View view, final DirEntry item, final int position) {
						///	Setup entry name such as filename or directory name.
						TextView text = (TextView)view.findViewById(R.id.text);
						
						String name = null;
						if(_isShort) {
							name = item.getShortName();
						} else {
							name = item.getLongName();
							if(name == null || name.equals(""))
								name = item.getShortName();
						}
						
						text.setText(name);

						///	Setup entry's icon.
						Resources res = getResources();
						Drawable icon = null;
						if(item.isDir())
							icon = res.getDrawable(R.drawable.folder_icon);
						else {
							icon = item.getIcon();
							if(icon == null)
								icon = getResources().getDrawable(R.drawable.file_icon);
						}
						
						ImageView image = (ImageView)view.findViewById(R.id.type_icon);
						image.setImageDrawable(icon);
						
						///	Setup click listener for this item.
						view.setOnClickListener(new OnClickListener() {
							@Override
							public void onClick(View v) {
								selectItem(item, position);
							}
						});
					}
				});
		
		///	Setup ListView object.
		_listView = (PullToRefreshListView)findViewById(R.id.dir_list);
		_listView.setAdapter(_adapter);
		_listView.setOnItemClickListener(new OnItemClickListener() {
			@Override
			public void onItemClick(
							AdapterView<?> parent,
							View view,
							int position,
							long id) {
				if(position >= _list.size())
					return;
				
				DirEntry item = _list.get(position);
				selectItem(item, position);
			}
		});
		_listView.setOnRefreshListener(
				new PullToRefreshListView.OnRefreshListener() {
					@Override
					public void onRefresh() {
						///	Invert Entry name mode.
						_isShort = !_isShort;
						
						///	Change entry name mode.
						SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
						Editor editor = pref.edit();
						editor.putString(
								getString(R.string.preference_entry_name_mode),
								_isShort ?
									getString(R.string.value_shortname) :
									getString(R.string.value_longname));
						editor.commit();

						///	Refresh directory entries.
						refreshList(_currentPath);
						
						///	Pull to refresh completed.
						_listView.onRefreshComplete();
					}
				});
		
		///	Register context menu to ListView object.
		registerForContextMenu(_listView);
		
		///	Setup ProgressDialog object.
		_progress = new ProgressDialog(this);
		_progress.setTitle(R.string.progress_title);
		_progress.setMessage(getResources().getText(R.string.progress_message));
		_progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
		_progress.setCancelable(true);
		_progress.setOnCancelListener(new OnCancelListener() {
			@Override
			public void onCancel(DialogInterface dialog) {
				_currentPathView.setText(_currentPath);
				cancelThread();
				
				if(_cacheManager.isExist(_currentPath))
					refreshList(_currentPath);
				else
					startThread(_currentPath, _encodingMode);
			}});
	}
	
	private void selectItem(DirEntry item, int position) {
		
		///	If item is file, single-click allows users to open the item.
		if(!item.isDir()) {
			openFile(position);
			return;
		}
		
		/// Go down directory hierarchy, selecting an item.
		String name = item.getLongName();
		if(name.equals(""))
			name = item.getShortName();
		
		///	But when ".." entries selected, we will go up directory hierarchy.
		String destination;
		if(name.equals(".."))
			destination = moveUpperLevel(_currentPath);
		else
			destination = (_currentPath + "/" + name);
		
		///	Remember current line number on current path.
		_cacheManager.setLineNumber(_currentPath, position);
		
		///	Go to the new directory.
		if(_cacheManager.isExist(destination))
			refreshList(destination);
		else
			startThread(destination, _encodingMode);

		///	Current path display changed.
		TextView path = (TextView)findViewById(R.id.dir_pos_text);
		path.setText(destination);
	}
	
	private void startThread(final String path, String encode) {
		_thread = new Thread(
			new DirEntryComposer(
					ShortnameExplorer.this,
					path,
					encode,
					new DirEntryComposer.Callback() {
						@Override
						public void finish(final List<DirEntry> list, boolean isCanceled) {
							if(isCanceled)
								return;
							
							_handler.post(new Runnable() {
								@Override
								public void run() {
									_cacheManager.add(path, list);
									refreshList(path);

									if(_progress.isShowing())
										_progress.dismiss();
								}
							});
						}
					}));
		
		_adapter.clear();
		_progress.show();
		_thread.start();
	}
	
	private void cancelThread() {
		if(_thread != null && _thread.isAlive()) {
			_thread.interrupt();
			try {
				_thread.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				_thread = null;
			}
		}
	}
	
	@Override
	protected void onResume() {
		super.onResume();
		
		SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(
																getApplicationContext());

		///	Get entry name mode.
		String nameMode = pref.getString(
								getString(R.string.preference_entry_name_mode),
								getString(R.string.value_shortname));
		_isShort = !nameMode.equals(getString(R.string.value_longname));
		
		///	Get encode mode.
		_encodingMode = pref.getString(
								getString(R.string.preference_encoding_mode),
								getString(R.string.value_sjis));

		///	安全のため？、サポート外の場合は日本仕様とする
		if(	!Charset.isSupported(_encodingMode))
			_encodingMode = getString(R.string.value_sjis);
		
		/// Setup current path label.
		TextView posText = (TextView)findViewById(R.id.dir_pos_text);
		posText.setText(_currentPath);
		
		///	Return current directory.
		if(_cacheManager.isExist(_currentPath)) {
			refreshList(_currentPath);
			return;
		}
		
		startThread(_currentPath, _encodingMode);
	}

	@Override
	protected void onPause() {
		super.onPause();
		
		if(_progress.isShowing())
			_progress.dismiss();
	}
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		///	戻るボタン以外は処理しない
		if(keyCode != KeyEvent.KEYCODE_BACK)
			return super.onKeyDown(keyCode, event);

		///	トップディレクトリ閲覧中の戻るはアプリを終了する
		if(_currentPath.equals(Environment.getExternalStorageDirectory().getPath()))
			return super.onKeyDown(keyCode, event);
		
		///	一階層上に移動する
		String destination = moveUpperLevel(_currentPath);

		///	「戻る」場合はカレント行をクリアする
		_cacheManager.setLineNumber(_currentPath, 0);
			
		if(_cacheManager.isExist(destination))
			refreshList(destination);
		else
			startThread(destination, _encodingMode);

		TextView path = (TextView)findViewById(R.id.dir_pos_text);
		path.setText(destination);
		
		return true;
	}
	
	private String moveUpperLevel(String origin) {
		///	文字列を分離して、末尾のみ切り離す
		String dest = "";
		String[] tmp = origin.split("\\/");
		for(int i = 1; i < tmp.length - 1; i ++) {
			dest += "/" + tmp[i];
		}
		
		return dest;
	}

	private void refreshList(String path) {
		///	まずはリストをクリアしておく
		_adapter.clear();

		int currentLine = 0;
		int num = _cacheManager.getEntryNum(path);
		
		///	トップディレクトリの場合は最初の2エントリ(.と..)は表示しない
		///	その他のディレクトリの場合は先頭エントリ(.)だけ表示しない
		for(int index = getDisplayStartIndex(path); index < num; index ++) {
			DirEntry entry = _cacheManager.getEntry(path, index);
			if(entry == null)
				continue;
			
			_adapter.add(entry);
		}
			
		currentLine = _cacheManager.getLineNumber(path);
		
		_adapter.notifyDataSetChanged();
		_listView.setSelection(currentLine + 1);
		
		_currentPath = path;
	}
	
	/**
	 * ディレクトリエントリの「表示用」開始インデックスを取得する。
	 * @param path
	 * @return
	 */
	private int getDisplayStartIndex(String path) {
		int index = 1;
		if(path.equals(Environment.getExternalStorageDirectory().toString()))
			index = 2;
		
		return index;
	}

	private static final int MENU_ITEM_PREFERENCES = 0;
	private static final int MENU_ITEM_APP_FINISH = 1;
	private static final int MENU_ITEM_ABOUT = 2;
	
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		///	オプションメニュー作成
		menu.add(0, MENU_ITEM_PREFERENCES,	0, R.string.option_menu_title_preferences);
		menu.add(0, MENU_ITEM_APP_FINISH,	0, R.string.option_menu_title_exit);
		menu.add(0, MENU_ITEM_ABOUT,	0, R.string.option_menu_title_about);
		
		return super.onCreateOptionsMenu(menu);
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch(item.getItemId()) {
		case MENU_ITEM_PREFERENCES:
			Intent intent = new Intent(ShortnameExplorer.this, ShortnamePreference.class);
			startActivity(intent);
			break;
			
		case MENU_ITEM_APP_FINISH:
			finish();
			break;
			
		case MENU_ITEM_ABOUT:
			startAboutDialog();
			break;
			
		default:
			break;
		}
		
		return super.onOptionsItemSelected(item);
	}
	
	private void startAboutDialog() {
		Resources res = getResources();
		InputStreamReader reader = new InputStreamReader(
						res.openRawResource(R.raw.pulltorefresh_license));

		new AlertDialog.Builder(ShortnameExplorer.this)
			.setTitle(R.string.about_dialog_title)
			.setMessage(new String(translateStreamToBytes(reader)))
			.setPositiveButton(
					getText(R.string.OK),
					new DialogInterface.OnClickListener() {
						@Override
						public void onClick(DialogInterface dialog, int which) {
							dialog.dismiss();
						}
			})
			.show();
		
		try {
			reader.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private byte[] translateStreamToBytes(InputStreamReader in) {
		ByteArrayOutputStream arrayStream = new ByteArrayOutputStream();
		OutputStream out = new BufferedOutputStream(arrayStream);
		
		try {
			int c;
			while((c = in.read()) != -1)
				out.write(c);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(out != null) {
				try {
					out.flush();
					out.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
		return arrayStream.toByteArray();
	}

	private static final int CTX_ITEM_OPEN = 0;
	private static final int CTX_ITEM_EDIT = 1;
	private static final int CTX_ITEM_SHARE = 2;
	private static final int CTX_ITEM_DELETE = 3;
	
	@Override
	public void onCreateContextMenu(
						ContextMenu menu,
						View view,
						ContextMenuInfo menuInfo) {
		///	選択された行の情報を取得する
		///	ただしディレクトリエントリの先頭「.」「..」は省略されているため、
		///	その分を考慮してエントリ情報を取得する
		int position = ((AdapterContextMenuInfo)menuInfo).position;
		DirEntry entry = null;
		entry = _cacheManager.getEntry(
							_currentPath,
							getDisplayStartIndex(_currentPath) + position);
		if(entry == null)
			return;
		
		///	選択行がディレクトリの場合はコンテキストメニューを出さない
		if(entry.isDir())
			return;
		
		super.onCreateContextMenu(menu, view, menuInfo);
		
		menu.add(0, CTX_ITEM_OPEN, 0, R.string.context_menu_open);
		menu.add(0, CTX_ITEM_EDIT, 0, R.string.context_menu_edit);
		menu.add(0, CTX_ITEM_SHARE, 0, R.string.context_menu_share);
		menu.add(0, CTX_ITEM_DELETE, 0, R.string.context_menu_delete);
	}

	@Override
	public boolean onContextItemSelected(MenuItem item) {
		boolean isDone = true;
		int position = ((AdapterContextMenuInfo)item.getMenuInfo()).position;
		switch(item.getItemId()) {
		case CTX_ITEM_OPEN:
			openFile(position);
			break;
		case CTX_ITEM_EDIT:
			editFile(position);
			break;
		case CTX_ITEM_SHARE:
			shareFile(position);
			break;
		case CTX_ITEM_DELETE:
			deleteFile(position);
			break;
		default:
			isDone = false;
			break;
		}
		
		if(!isDone)
			return super.onContextItemSelected(item);
		
		return true;
	}
	
	private void openFile(int position) {
		if(_list.size() <= position)
			return;
		
		Intent intent = createIntent(Intent.ACTION_VIEW, position);
		
		try {
			startActivity(intent);
		} catch(ActivityNotFoundException e) {
			Toast.makeText(
					getApplicationContext(),
					R.string.app_not_found,
					Toast.LENGTH_SHORT).show();
		}
	}
	
	private Intent createIntent(String action, int position) {
		Assert.assertTrue(_list.size() > position);
		
		///	ファイルパスを指定して閲覧用Intentを送信する
		String fileName = _list.get(position).getLongName();
		if(fileName.equals(""))
			fileName = _list.get(position).getShortName();

		String path = _currentPath + "/" + fileName;
		Uri uri = Uri.fromFile(new File(path));
		String mimeType = MimeUtilities.getMimeType(fileName);
		
		///	ただしActionに関しては呼び出し元で指定する
		Intent intent = new Intent();
		intent.setAction(action);
		intent.setDataAndType(uri, mimeType);

		return intent;
	}
	
	private void editFile(int position) {
		if(position >= _list.size())
			return;
		
		Intent intent = createIntent(Intent.ACTION_EDIT, position);
		
		try {
			startActivity(intent);
		} catch(ActivityNotFoundException e) {
			Toast.makeText(
					getApplicationContext(),
					R.string.app_not_found,
					Toast.LENGTH_SHORT).show();
		}
	}
	
	private void deleteFile(final int position) {
		if(position >= _list.size())
			return;
		
		///	削除確認ダイアログを表示する
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		AlertDialog dialog = builder.create();
		dialog.setMessage(getString(R.string.delete_confirm));
		dialog.setButton(
					getString(R.string.OK),
					new DialogInterface.OnClickListener() {
						@Override
						public void onClick(DialogInterface dialog, int which) {
							String fileName = _list.get(position).getLongName();
							if(fileName.equals(""))
								fileName = _list.get(position).getShortName();
							
							File file = new File(_currentPath + "/" + fileName);
							boolean result = file.delete();
							if(!result) {
								Toast.makeText(
										getApplicationContext(),
										getString(R.string.delete_failed),
										Toast.LENGTH_SHORT).show();
								return;
							}
							
							///	削除が成功したらリストを再構築する
							///	なお、これまで表示していたリストのキャッシュを破棄する
							_cacheManager.deleteCacheEntry(_currentPath);
							startThread(_currentPath, _encodingMode);
							_progress.show();
						}
					});
		dialog.setButton2(
					getString(R.string.cancel),
					new DialogInterface.OnClickListener() {
						@Override
						public void onClick(DialogInterface dialog, int which) {
							dialog.cancel();
						}
					});
		dialog.setCancelable(true);
		dialog.show();
	}
	
	private void shareFile(int position) {
		if(position >= _list.size())
			return;
		
		Intent intent = createIntent(Intent.ACTION_SEND, position);
		
		try {
			startActivity(intent);
		} catch(ActivityNotFoundException e) {
			Toast.makeText(
					getApplicationContext(),
					R.string.app_not_found,
					Toast.LENGTH_SHORT).show();
		}
	}
	
	private static String STATUS_KEY_CURRENT_PATH = "CURRENT_PATH";
	
	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);

		outState.putString(STATUS_KEY_CURRENT_PATH, _currentPath);
	}

	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState) {
		super.onRestoreInstanceState(savedInstanceState);
		
		_currentPath = savedInstanceState.getString(STATUS_KEY_CURRENT_PATH);
	}
}